package com.search;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.mapdb.DB;
import org.mapdb.DBMaker;

import com.data.JDoc;

public class QueryAnalyzer {

	// errors
	public ArrayList<String> errors = new ArrayList<String>();

	// search result
	private QueryResult queryResult;
	private Stemmer stemmer;

	private ArrayList<String> stemmedQuery = new ArrayList<String>();
	private ArrayList<ArrayList<String>> stemmedPhrase = new ArrayList<ArrayList<String>>();

	// scores results
	private Map<Integer, Float> innerProduct = new HashMap<Integer, Float>();
	private Map<Integer, Float> docLength = new HashMap<Integer, Float>();
	private Map<Integer, Float> cosScore = new HashMap<Integer, Float>();
	private Map<Integer, Float> titleScore = new HashMap<Integer, Float>();
	private Map<Integer, Float> phraseScore = new HashMap<Integer, Float>();
	private Map<Integer, Float> totalScore = new HashMap<Integer, Float>();
	private ArrayList<Integer> ranking = new ArrayList<Integer>();

	float maxCosScore = 0;
	float maxTitleScore = 0;
	float maxPhraseScore = 0;

	// db table
	Map<Integer, JDoc> idToDoc;
	Map<String, Map<Integer, Float>> invertedIndex;

	public QueryAnalyzer(String stopWordPath, String dbPath)
			throws FileNotFoundException {
		queryResult = new QueryResult();

		stemmer = new Stemmer(stopWordPath);

		// initialize db
		DB db = DBMaker.newFileDB(new File(dbPath)).closeOnJvmShutdown().make();
		idToDoc = db.getHashMap("idToDoc");
		invertedIndex = db.getHashMap("invertedIndex");

	}

	public QueryResult query(String inputQuery) {

		stemmer.stemQuery(inputQuery);
		stemmedQuery = stemmer.getStemmedQuery();
		stemmedPhrase = stemmer.getStemmedPhrase();

		cosSimilarity();
		titleScore();
		phraseInBody();
		phraseInTitle();
		ranking();

		queryResult.setResult(idToDoc);
		queryResult.setScores(totalScore);
		queryResult.setRanking(ranking);

		return queryResult;
	}

	public void cosSimilarity() {

		// Calculation of inner product
		for (String term : invertedIndex.keySet()) {
			Map<Integer, Float> docTwList = invertedIndex.get(term);
			if (stemmedQuery.contains(term)) {
				for (Integer docId : docTwList.keySet()) {
					if (innerProduct.containsKey(docId)) {
						innerProduct.put(docId, innerProduct.get(docId)
								+ docTwList.get(docId));
					} else {
						innerProduct.put(docId, docTwList.get(docId));
					}
				}
			}
			// Calculation of doc length
			for (Integer docId : docTwList.keySet()) {
				if (docLength.containsKey(docId)) {
					docLength.put(
							docId,
							docLength.get(docId)
									+ (docTwList.get(docId) * docTwList
											.get(docId)));
				} else
					docLength.put(docId,
							docTwList.get(docId) * docTwList.get(docId));
			}
		}

		// Calculation of COS similarity (innerProduct/(sqrt(docLength) *
		// sqrt(queryLength))
		for (Integer id : innerProduct.keySet()) {
			float score = (float) (innerProduct.get(id) / (Math.sqrt(docLength
					.get(id)) * Math.sqrt(stemmedQuery.size())));
			cosScore.put(id, score);
		}

		for (Integer id : cosScore.keySet()) {
			if (cosScore.get(id) > maxCosScore)
				maxCosScore = cosScore.get(id);
		}

	}

	public void titleScore() {

		for (Integer id : idToDoc.keySet()) {
			ArrayList<String> stemmedTitle = idToDoc.get(id).getStemmedTitle();
			for (String q : stemmedQuery) {
				if (stemmedTitle.contains(q)) {
					if (titleScore.containsKey(id)) {
						titleScore.put(id, titleScore.get(id) + (float) 1);
					} else
						titleScore.put(id, (float) 1);
				}
			}
		}

		for (Integer id : titleScore.keySet()) {
			if (titleScore.get(id) > maxTitleScore)
				maxTitleScore = titleScore.get(id);
		}
	}

	public void phraseInBody() {

		for (Integer id : idToDoc.keySet()) {
			int phraseFreq = 0;
			ArrayList<String> stemmedBody = idToDoc.get(id).getStemmedBody();
			for (ArrayList<String> phrase : stemmedPhrase) {
				for (int i = 0; i < stemmedBody.size(); i++) {
					for (int j = 0; j < phrase.size(); j++) {
						if (i == stemmedBody.size() - 1 || !phrase.get(j).equals(stemmedBody.get(i + j))) {
							break;
						} else if (j == phrase.size() - 1){
							phraseFreq += 1;
						System.out.println(phraseFreq);}
					}

				}
			}
			phraseScore.put(id, (float) phraseFreq);

		}
		for (Integer id : phraseScore.keySet()) {
			if (phraseScore.get(id) > maxPhraseScore)
				maxPhraseScore = phraseScore.get(id);
		}
	}

	public void phraseInTitle() {

		for (Integer id : idToDoc.keySet()) {
			int phraseFreq = 0;
			ArrayList<String> stemmedTitle = idToDoc.get(id).getStemmedTitle();
			for (ArrayList<String> phrase : stemmedPhrase) {
				for (int i = 0; i < stemmedTitle.size(); i++) {
					for (int j = 0; j < phrase.size(); j++) {
						if (!phrase.get(j).equals(stemmedTitle.get(i + j))) {
							break;
						} else if (j == phrase.size() - 1)
							phraseFreq += 1;
					}

				}
			}
			if (titleScore.keySet().contains(id))
				titleScore.put(id, (float) phraseFreq + titleScore.get(id));
			else
				titleScore.put(id, (float) phraseFreq);

		}
		for (Integer id : titleScore.keySet()) {
			if (titleScore.get(id) > maxTitleScore)
				maxTitleScore = titleScore.get(id);
		}
	}

	public void ranking() {

		for (Integer id : idToDoc.keySet()) {
			float cos = 0;
			float title = 0;
			float phrase = 0;
			if (cosScore.keySet().contains(id))
				cos = cosScore.get(id) / maxCosScore;

			if (titleScore.get(id) != 0)
				title = titleScore.get(id) / maxTitleScore;
			if (phraseScore.get(id) != 0)
				phrase = phraseScore.get(id) / maxPhraseScore;
			totalScore.put(id, (cos + title + phrase) / 3);
		}
		

		// Convert Map to List
		List<Map.Entry<Integer, Float>> list = new LinkedList<Map.Entry<Integer, Float>>(
				totalScore.entrySet());

		// Sort list with comparator, to compare the Map values
		Collections.sort(list, new Comparator<Map.Entry<Integer, Float>>() {
			public int compare(Map.Entry<Integer, Float> o1,
					Map.Entry<Integer, Float> o2) {
				return (o2.getValue()).compareTo(o1.getValue());
			}
		});

		// Convert sorted map back to a Map
		for (Iterator<Map.Entry<Integer, Float>> it = list.iterator(); it
				.hasNext();) {
			Map.Entry<Integer, Float> entry = it.next();
			if (entry.getValue() > (float) 0)
				ranking.add(entry.getKey());
		}
	}

	public void cleanResult() throws FileNotFoundException {
		if (queryResult.getRanking().size() > 0) {
			queryResult = new QueryResult();
		}

		stemmer.cleanStemmer();
		stemmedQuery = new ArrayList<String>();
		stemmedPhrase = new ArrayList<ArrayList<String>>();

		innerProduct = new HashMap<Integer, Float>();
		docLength = new HashMap<Integer, Float>();
		cosScore = new HashMap<Integer, Float>();
		titleScore = new HashMap<Integer, Float>();
		phraseScore = new HashMap<Integer, Float>();
		totalScore = new HashMap<Integer, Float>();
		ranking = new ArrayList<Integer>();

		maxCosScore = 0;
		maxTitleScore = 0;
		maxPhraseScore = 0;
	}

	public static boolean isEmpty(String input) {

		if (input != null && input.length() > 0) {
			String temp = input.trim();
			if (!temp.isEmpty()) {
				return false;
			}
		}
		return true;
	}

	public TreeMap<String, Integer> sortHashMap(Map<String, Integer> unsortedMap) {
		ValueComparator bvc = new ValueComparator(unsortedMap);
		TreeMap<String, Integer> sortedTreeMap = new TreeMap<String, Integer>(
				bvc);

		sortedTreeMap.putAll(unsortedMap);

		return sortedTreeMap;
	}

	class ValueComparator implements Comparator<String> {

		Map<String, Integer> base;

		public ValueComparator(Map<String, Integer> base) {
			this.base = base;
		}

		// Note: this comparator imposes orderings that are inconsistent with
		// equals.
		public int compare(String a, String b) {
			if (base.get(a) >= base.get(b)) {
				return -1;
			} else {
				return 1;
			} // returning 0 would merge keys
		}
	}
}